﻿// Nail framework
//
// Copyright © Frank Nagl, 2009
// admin@franknagl.de
// www.franknagl.de
//
namespace Nail
{
    using System.Drawing;
    using System.Drawing.Imaging;
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// Floodfills the segments of a colored image.
    /// CAUTION: Recursion doesn't work --> Stack overflow
    /// </summary>
    public unsafe class RecursiveSegmentFloodFill
    {
        private const byte colNumber = 32;
        // Color table for coloring regions
        private static Color[] colorTable = new Color[colNumber]
        {
            Color.Red,		Color.Green,	Color.Blue,			Color.Yellow,
            Color.Violet,	Color.Brown,	Color.Olive,		Color.Cyan,

            Color.Aquamarine,	Color.Gold,		Color.Indigo,		Color.Ivory,
            Color.Azure,	Color.DarkRed,	Color.DarkGreen,	Color.DarkBlue,

            Color.DarkSeaGreen,	Color.Gray,	Color.DarkKhaki,	Color.DarkGray,
            Color.LimeGreen, Color.Tomato,	Color.SteelBlue,	Color.SkyBlue,

            Color.Silver,	Color.Salmon,	Color.SaddleBrown,	Color.RosyBrown,
            Color.PowderBlue, Color.Plum,	Color.PapayaWhip,	Color.Orange
        };

        byte* values;
        bool[] processed;
        int stride;
        int smallPos;
        int bigPos;
        int height;
        int width;
        int counter = 0;

        /// <summary>
        /// Initializes a new instance of the <see cref="RecursiveSegmentFloodFill"/> class.
        /// </summary>
        public RecursiveSegmentFloodFill() { }

        /// <summary>
        /// Extracts the color segments of the original bitmap as pixel-connected regions.
        /// </summary>
        public void ProcessImage(Bitmap image)
        {
            // check image format
            if (image.PixelFormat != PixelFormat.Format24bppRgb)
            {
                throw new ArgumentException("Source image can be color (24 bpp) image only");
            }

            width = image.Width;
            height = image.Height;
            Rectangle rect = new Rectangle(0, 0, width, height);

            // lock source bitmap data
            BitmapData imageData = image.LockBits(
                rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            // image sizes
            stride = imageData.Stride;
            processed = new bool[width * height];
            int colorId = 0;
            int pos = stride;

            // process image
            //unsafe
            {
                values = (byte*)imageData.Scan0.ToPointer();//
                byte r = values[pos + RGB.R];
                byte g = values[pos + RGB.G];
                byte b = values[pos + RGB.B];
                values[pos + RGB.R] = colorTable[colorId].R;
                values[pos + RGB.G] = colorTable[colorId].G;
                values[pos + RGB.B] = colorTable[colorId].B;

                smallPos = stride * height;
                bigPos = -1;
                CheckNeighbours(pos, colorId, r, g, b);

                while (smallPos != (stride * height) || bigPos != -1)
                {
                    colorId++;
                    if (colorId == 31)
                        colorId = 0;

                    if (smallPos != (stride * height))
                        pos = smallPos;
                    else
                        pos = bigPos;
                    smallPos = stride * height;
                    bigPos = -1;

                    r = values[pos + RGB.R];
                    g = values[pos + RGB.G];
                    b = values[pos + RGB.B];
                    values[pos + RGB.R] = colorTable[colorId].R;
                    values[pos + RGB.G] = colorTable[colorId].G;
                    values[pos + RGB.B] = colorTable[colorId].B;
                    CheckNeighbours(pos, colorId, r, g, b);

                    Console.WriteLine("counter: " + counter);
                    counter = 0;
                }
            }

            //// Copy the RGB values back to the bitmap
            //System.Runtime.InteropServices.Marshal.Copy(values, 0, scan0, arraySize);

            // unlock destination image
            image.UnlockBits(imageData);            
        }

        private void CheckNeighbours(int pos, int colorId, byte r, byte g, byte b)
        {
            if (processed[pos / 3])
                return;

            processed[pos / 3] = true;
            counter++;

            if (pos < stride ||                         // 1st line
                pos > ((height - 1) * stride - 1))      // last px of penultimate line
            {
                values[pos + RGB.R] = 0;
                values[pos + RGB.G] = 0;
                values[pos + RGB.B] = 0;
                return;
            }
            else
            {                
                //UP NEIGHBOUR
                bool up = IsNeighbour(pos - stride, colorId, stride, height, r, g, b, ref smallPos, ref bigPos);

                //LEFT NEIGHBOUR
                bool left = IsNeighbour(pos - 3, colorId, stride, height, r, g, b, ref smallPos, ref bigPos);
                
                //RIGHT NEIGHBOUR
                bool right = IsNeighbour(pos + 3, colorId, stride, height, r, g, b, ref smallPos, ref bigPos);
                
                //DOWN NEIGHBOUR
                bool down = IsNeighbour(pos + stride, colorId, stride, height, r, g, b, ref smallPos, ref bigPos);

                if (up) CheckNeighbours(pos - stride, colorId, r, g, b);
                if (left) CheckNeighbours(pos - 3, colorId, r, g, b);
                if (right) CheckNeighbours(pos + 3, colorId, r, g, b);
                if (down) CheckNeighbours(pos + stride, colorId, r, g, b); 
            }
        }

        private bool IsNeighbour(int pos, int colorId, int stride, int height, //byte[] values,
                        byte r, byte g, byte b, ref int small, ref int big)
        {
            if (r == values[pos + RGB.R] &&
                g == values[pos + RGB.G] &&
                b == values[pos + RGB.B])
            {
                values[pos + RGB.R] = colorTable[colorId].R;
                values[pos + RGB.G] = colorTable[colorId].G;
                values[pos + RGB.B] = colorTable[colorId].B;
                
                return true;
            }
            else
            {
                if (processed[pos / 3])
                    return false;

                if (pos > stride && pos < small)
                    small = pos;
                else if (pos < ((height - 1) * stride - 1) && pos > big)
                    big = pos;
                return false;
            }
        }

        private int IndexOfValues()
        {
            int index = -1;
            for (int i = stride; i < ((height - 1) * stride - 1); i += 3)
                if (!processed[i / 3])
                    return i;
            return index;
        }
    }
}
